/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.commands.CommandHandlersUtil.ValidationException;
import com.hercules.android.receiver.handlers.events.EventRaiser;
import com.hercules.android.receiver.handlers.events.EventRaisingException;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XREGetCommand;
import com.hercules.android.receiver.model.events.XREErrorEvent;
import com.hercules.android.receiver.model.events.XREGetCompleteEvent;
import com.hercules.android.receiver.model.objects.XREObject;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

/**
 * The handler used to process XREGetCommand. It will query the specified XREObject. It extends XRECommandHandler for
 * convenience.
 * <p>
 * AndroidAssembly3 this class was added for Android Assembly 3.
 * <p>
 * <b>Thread Safety:</b> This class is not thread safe since it has mutable fields. But if they are not changed after
 * first configured, then this class is used in a thread safe manner.
 *
 * @author TCSASSEMBLER
 * @version 1.0
 */
public class XREGetCommandHandler extends XRECommandHandler {
    /**
     * The event raiser for {@link XREGetCompleteEvent} event.
     * <p>
     * It has getter and setter. Cannot be null after set. Is used in processCommandMessage method.
     */
    private EventRaiser eventRaiser;

    /**
     * Default empty constructor.
     */
    public XREGetCommandHandler() {
        // empty
    }

    /**
     * Gets the event raiser for {@link XREGetCompleteEvent} event.
     *
     * @return the event raiser for {@link XREGetCompleteEvent} event
     */
    public EventRaiser getEventRaiser() {
        return eventRaiser;
    }

    /**
     * Sets the event raiser for {@link XREGetCompleteEvent} event.
     *
     * @param eventRaiser the event raiser for {@link XREGetCompleteEvent} event
     */
    public void setEventRaiser(EventRaiser eventRaiser) {
        this.eventRaiser = eventRaiser;
    }

    /**
     * Handle given GET command message. The target resource or view will be queried.
     *
     * @param command the XRE GET command to be handled
     * @param receiver the receiver for the XRE application
     * @throws IllegalStateException if event raiser is not set
     * @throws IllegalArgumentException if given command or receiver is null
     * @throws XREObjectNotFoundException if the XREObject to be updated or any XREObject to be associated is not found
     * @throws XREObjectCommandHandlerException if any other error occurs while handling the command
     */
    @SuppressWarnings("boxing")
    @Override
    protected void processCommandMessage(XRECommand command, AndroidReceiver receiver)
        throws XREObjectCommandHandlerException {
        String methodName = "processCommandMessage";
        LogUtil.enter(getLogger(), this, methodName, command, receiver);

        try {
            // check state
            CommandHandlersUtil.checkState(eventRaiser, "eventRaiser");

            // check arguments
            XREGetCommand getCommand = CommandHandlersUtil.checkCommand(command, XREGetCommand.class);
            CommandHandlersUtil.checkReceiver(receiver);

            // get object by given id
            XREObject xreObject =
                receiver.getApplicationData().getElementsXREIdToObject().get(getCommand.getTargetId());
            if (xreObject == null) {
                throw new XREObjectNotFoundException("Object with given ID '" + getCommand.getTargetId()
                    + "' does not exist.", getCommand.getTargetId());
            }

            // prepare event
            XREGetCompleteEvent event = new XREGetCompleteEvent();
            event.setName(XREGetCompleteEvent.ON_GET_COMPLETE_EVENT_NAME);
            event.setSource(getCommand.getTargetId());
            event.setContext(getCommand.getContext());
            event.setProps(new HashMap<String, Object>());

            try {
                // get property values
                for (String propertyName : getCommand.getProps()) {
                    Object propertyValue = getPropertyValue(xreObject, propertyName);
                    if (propertyValue instanceof XREObject) {
                        propertyValue = ((XREObject) propertyValue).getId();
                    }

                    event.getProps().put(propertyName, propertyValue);
                }

                // raise event
                eventRaiser.raiseEvent(event, receiver, true);
            } catch (XREObjectCommandHandlerException e) {
                // raise onError event
                XREErrorEvent errorEvent = new XREErrorEvent();
                errorEvent.setName(XREErrorEvent.ON_ERROR_EVENT_NAME);
                errorEvent.setSource(getCommand.getTargetId());
                errorEvent.setErrorType(XREErrorEvent.ERROR_LOAD_FAILED_TYPE);

                // raise event
                eventRaiser.raiseEvent(errorEvent, receiver, true);
            }
            LogUtil.exit(getLogger(), this, methodName);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (IllegalStateException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (XREObjectCommandHandlerException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (ValidationException e) {
            throw LogUtil.error(getLogger(), this, methodName, new XREObjectCommandHandlerException(e.getMessage(), e));
        } catch (EventRaisingException e) {
            throw LogUtil.error(getLogger(), this, methodName, new XREObjectCommandHandlerException("Failed to raise '"
                + XREGetCompleteEvent.ON_GET_COMPLETE_EVENT_NAME + "' event.", e));
        }
    }

    /**
     * Gets the property value of the object by name.
     *
     * @param obj the object
     * @param propertyName the property name
     * @return the property value
     * @throws XREObjectCommandHandlerException if any error occurs.
     */
    private static Object getPropertyValue(Object obj, String propertyName) throws XREObjectCommandHandlerException {
        String upperCasePropertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
        try {
            return executeMethod(obj, "get" + upperCasePropertyName);
        } catch (NoSuchMethodException e) {
            try {
                return executeMethod(obj, "is" + upperCasePropertyName);
            } catch (NoSuchMethodException e1) {
                throw new XREObjectCommandHandlerException("The property '" + propertyName
                    + "' cannot be found at class '" + obj.getClass().getName() + "'.", e);
            }
        }
    }

    /**
     * Executes getter method.
     *
     * @param obj the object
     * @param getterName the name of getter
     * @return method result
     * @throws NoSuchMethodException if method with given name is not found
     * @throws XREObjectCommandHandlerException if error occurs at method execution
     */
    private static Object executeMethod(Object obj, String getterName) throws XREObjectCommandHandlerException,
        NoSuchMethodException {
        try {
            return obj.getClass().getMethod(getterName).invoke(obj);
        } catch (IllegalAccessException e) {
            throw new XREObjectCommandHandlerException("Failed to access the getter '" + getterName + "'.", e);
        } catch (InvocationTargetException e) {
            throw new XREObjectCommandHandlerException("Failed to invoke the getter '" + getterName + "'.", e);
        } catch (SecurityException e) {
            throw new XREObjectCommandHandlerException("A security error occurred.", e);
        }
    }

}